#!/usr/bin/python3
# coding=utf-8
"""
# licensed under the Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#     http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the Mulan PSL v2 for more details.
# Author: YangYunYi
# Create: 2020-11-04
"""
import os
import enum
import shutil
import autoimport.getrequires.require_graph as RequireGraph
import autoimport.logmodule.logger as logger

CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
DATA_PATH = "data"
OUTPUT_PATH = "output"
INSTALL_LINE_STEP = 4
BUILD_LINE_STEP = 3

this_logger = logger.init('getrequires')
logger.add_file_handler(this_logger, "getrequires", "getrequires")


class RequiresRecord:
    """
    依赖数据记录
    """
    def __init__(self):
        self.pkg_graph = RequireGraph.PkgGraph()
        self.circle_requires = set()
        self.src_2_repo = {}


class RequiresType(enum.Enum):
    """
    依赖类型
    """
    Build = 1
    Install = 2


def init_workspace():
    """
    初始化工作目录
    :return:无
    """
    dirs = [OUTPUT_PATH]
    for init_dir in dirs:
        # 初始化路径
        dir_path = os.path.join(CURRENT_PATH, init_dir)
        if os.path.exists(dir_path):
            shutil.rmtree(dir_path)
        this_logger.info("init_workspace dir path:%s", dir_path)
        os.makedirs(dir_path, exist_ok=True)


def get_file_path(sub_dir, file_name):
    """
    根据子目录与文件拼接处文件路径
    :param sub_dir:
    :param file_name:
    :return:拼接路径值
    """
    if str(sub_dir) != "":
        sub_dir = os.path.join(CURRENT_PATH, sub_dir)
        file_path = os.path.join(sub_dir, file_name)
    else:
        file_path = os.path.join(CURRENT_PATH, file_name)
    return file_path


def is_openEuler(repo):
    """
    判断repo是否是openEuler
    :param repo:
    :return: True-是，False-不是
    """
    if repo.find('openEuler') == -1:
        return False
    return True


def add_src_repo_info(src_name, repo, src_2_repo):
    """
    添加单元依赖类型,如果已经记录了软件包的repo是openEuler，则不再更新。openEuler优先
    :param src_name:源码包名
    :param repo:仓库包名
    :param src_2_repo:记录源码包仓库的列表
    :return:
    """
    # 更新源码包与仓对应关系：已经存在并且是openEuler了，就不用更新了
    if src_name not in src_2_repo.keys() or \
            not is_openEuler(src_2_repo[src_name]):
        src_2_repo[src_name] = repo


def get_fedora_src_list(src_list, src_2_repo):
    """
    根据二进制包列表，获取是fedora的源码包列表，如果不是fedora的包将从列表中删除
    :param src_list: 源码包列表
    :param src_2_repo: 记录源码包仓库的列表
    :return: 过滤后的源码包列表
    """
    output_src = src_list
    for item in src_list:
        if item not in src_2_repo.keys():
            continue
        repo_name = src_2_repo[item]
        if is_openEuler(repo_name):
            this_logger.info("item %s both found in fedora and openEuler.", item)
            output_src.remove(item)
    this_logger.info("get_fedora_src_list succeed. Total:%d.", len(output_src))
    return output_src


def judge_circle(line_list, step_len):
    """
    判断依赖路径是否有环,每一行的最后一个cell如果是1,则1前面的那个包是在这一行已经出现过的，为循环依赖
    :param line_list: 行数据列表
    :param step_len: 判断步长
    :return: True-有环；False-无环
    """
    line_len = len(line_list)
    if (line_len % step_len == 1) and line_list[-1] == '1':
        return True
    return False


def create_graph(file_name, requires_type, requires_record):
    """
    构建图
    :param file_name: 文件名称
    :param requires_type: 依赖类型
    :param requires_record: 记录数据
    :return: True-成功；false-失败
    """
    file_path = get_file_path(DATA_PATH, file_name)
    if not os.path.isfile(file_path):
        this_logger.error("%s is not existed.", file_path)
        return False

    line_step = BUILD_LINE_STEP if requires_type == RequiresType.Build else INSTALL_LINE_STEP
    this_logger.info("Create graph begin. csv file_name:%s, requires_type:%s.",
                     file_name, requires_type)
    with open(file_path, "r") as csv_file:
        for line in csv_file:
            data_list = list(line.strip().split(','))
            line_len = len(data_list)
            has_circle = judge_circle(data_list, line_step)
            this_logger.debug("Length of this line is % has_circle %d.", line_len, int(has_circle))
            if has_circle:
                line_len -= (line_step + 1)
                if not is_openEuler(data_list[-2]):
                    requires_record.circle_requires.add(data_list[-4])

            pre_pkg = ""
            src_list = set()
            for i in range(0, line_len, line_step):
                cur_pkg = data_list[i]
                cur_repo = data_list[i + line_step - 1]
                if requires_type == RequiresType.Install:
                    add_src_repo_info(data_list[i + 1], cur_repo, requires_record.src_2_repo)
                    if data_list[i + 1] in src_list:
                        continue
                    else:
                        cur_pkg = data_list[i + 1]
                        src_list.add(data_list[i + 1])

                if not is_openEuler(cur_repo):
                    # 添加包节点和依赖关系
                    if pre_pkg != "":
                        requires_record.pkg_graph.add_edge(cur_pkg, pre_pkg)
                    else:
                        requires_record.pkg_graph.add_vertex(cur_pkg)
                    pre_pkg = cur_pkg

    this_logger.info("Create graph end. vertex num:%d, edge num:%d.",
                     len(requires_record.pkg_graph.vertex_list),
                     len(requires_record.pkg_graph.edge_list))
    return True


def dump_list(file_name, data_list, mode='w'):
    """
    打印列表
    :param file_name: 文件名
    :param data_list: 数据列表
    :param mode: 文件打开模式
    :return: 无
    """
    file_path = get_file_path(OUTPUT_PATH, file_name)
    with open(file_path, mode) as dump_file:
        for value in data_list:
            print(value, end='\n', file=dump_file)

    this_logger.info("Dump list to %s succeed.", file_name)


def get_requires_sort(requires_record):
    """
    将依赖排序
    :param requires_record: 记录数据
    :return: 排序列表与待破环软件包列表
    """
    ret, sort_list = requires_record.pkg_graph.topo_sort()
    dump_list('sort_list1.txt', sort_list, 'w+')
    sort_list = get_fedora_src_list(sort_list, requires_record.src_2_repo)

    if ret:
        print('succeed!')
    else:
        print('there is something wrong with circle find.')
        dump_list('vertex.txt', requires_record.pkg_graph.get_vertices(), 'w+')

    dump_list('sort_list.txt', sort_list, 'w+')
    dump_list('circle_list.txt', requires_record.circle_requires, 'w+')


def get_csv_data(pkg_list):
    """
    获取所有CSV文件
    :param pkg_list: 软件包列表
    :return: CSV文件列表
    """
    files = os.listdir(DATA_PATH)
    csv_file_list = {}
    for pkg in pkg_list:
        install_name = "{}_install_res.csv".format(pkg)
        build_name = "{}_build_res.csv".format(pkg)
        if install_name not in files:
            install_name = ""
            this_logger.warning("Can not find install requires file %s csv of %s.", install_name, pkg)

        if build_name not in files:
            build_name = ""
            this_logger.warning("Can not find build requires file %s csv of %s.", build_name, pkg)

        csv_file = (install_name, build_name)
        csv_file_list.update({pkg: csv_file})

    return csv_file_list


def get_input_pkg_list():
    """
    获取待分析软件的列表
    :return: 软件包名列表
    """
    # 获取输入的所有包
    data_path = get_file_path('', 'input_package_list.txt')
    if not os.path.isfile(data_path):
        this_logger.error(
            "Not found the inputPackageList.txt file:%s.", data_path)
        return False
    input_pkg = set()
    with open(data_path, "r") as data_path:
        for line in data_path:
            input_pkg.add(line.strip())
        message = "    ".join(input_pkg)
        this_logger.debug("Init package list :%s.", message)
        return input_pkg


def build_requires_graph(csv_file_list, requires_record):
    """
    构建依赖图
    :param csv_file_list: CSV文件列表
    :param requires_record: 记录数据
    :return: 无
    """
    for item in csv_file_list.values():
        if item[0] != "":
            csv_file_name = item[0]
            create_graph(csv_file_name, RequiresType.Install, requires_record)
        if item[1] != "":
            csv_file_name = item[1]
            create_graph(csv_file_name, RequiresType.Build, requires_record)


def show_graph(graph):
    """
    显示依赖图
    :param graph: 依赖图
    :return: 无
    """
    # 显示
    graph_file_path = get_file_path(OUTPUT_PATH, 'graph.txt')
    graph.print(graph_file_path)
    pic_file_path = get_file_path(OUTPUT_PATH, 'pic.jpg')
    graph.draw_graph(pic_file_path)


def main():
    """
    主函数
    :return: 无
    """
    # 初始化工作目录
    init_workspace()
    input_pkg = get_input_pkg_list()
    csv_file_list = get_csv_data(input_pkg)
    # 构建依赖图
    record_data = RequiresRecord()
    build_requires_graph(csv_file_list, record_data)
    # 排序
    get_requires_sort(record_data)
    # 显示
    show_graph(record_data.pkg_graph)


if __name__ == '__main__':
    main()
